<?php

namespace service\express\Util;

use artisan\http;
use artisan\cache as Redis;
use artisan\db;
use service\proxy\ValidWaybillPool;

/**
 * 检查使用有效单号检查是否真的被封
 * 如何触发? 队列? 任务执行? 由于有实时问题,所以应该即时检查!!!!
 * __destruct???? ignore_user_abort ??? 异步?
 *
 * 使用现有的构建检测,可以调用即可.
 *
 */
class BlacklistedChecker
{
    protected static $proxy = null;
    protected static $via_ip = null;
    public static $ip_api = '';

    private static $db = null;
    private static $db_name = 'express';
    private static $table_name = 'express_proxy_blacklist';

    private static $error;

    public static $proxy_type = '';

    const ENABLE_ANALYSIS = false;

    private static $test_sources = [

    ];

    /**
     * doCheck
     *
     * @param  string|array $proxy
     * @param  string|array $type
     * @param  string $brand
     * @param  integer $check_times
     * @param  string|null $via_ip
     * @return mixed
     */
    public static function checkSource($proxy, $type = null, $brand = null, $check_times = 1, $via_ip = null)
    {
        if (is_array($proxy)) {
            $configs = $proxy;
            if ($proxy = $configs['prxoxy']) {
                !$type and isset($configs['type']) and $type = $configs['type'];
                !$brand and isset($configs['brand']) and $brand = $configs['brand'];
                isset($configs['check_times']) and $check_times = $configs['check_times'];
                isset($configs['via_ip']) and $via_ip = $configs['via_ip'];
            }
        } elseif (!$proxy) {
            return false;
        }
        static::$via_ip = static::getProxyIp($proxy);
        if ($via_ip !== null && static::$via_ip !== $via_ip) {
            return false;
        }
        $result = [];
        if (static::ipCheck(static::$via_ip, is_string($type) ? $type : null)) {
            return $result;
        }
        if (is_string($type) && ($type = trim($type))) {
            $type = [$type];
        }
        if (is_array($type)) {
            foreach ($type as $sources) {
                if ($waybills = static::getWaybills((int)$check_times + 10, $brand ?: static::getBrandBySource($sources)) and is_array($waybills)) {
                    $waybill_info = $waybills[array_rand($waybills)];
                    if (!$result[$waybill_info] = static::makeRequest($waybill_info, $sources)) {
                        static::addToBlacklist($waybill_info, $proxy);
                    }
                }
                //!in_array(static::$check_maps, $type) and continue;
            }
            return $result;
        }
        static::$error = 'data source not specified';
    }

    /**
     * makeRequest
     *
     * @param array $data
     * @return boolean
     */
    public static function makeRequest(array $data = [], $source = null)
    {
        $uri_tpl = 'brand=%s&no=%s&source=%s';
        $uri = sprintf($uri_tpl, $data['brand'], $data['waybill_no'], $source ?: $data['source']);
        $return = http::get($uri);
        return $return !== 'false';
    }

    /**
     * addToBlacklist
     *
     * @param array $data
     * @param string $proxy
     * @return mixed
     */
    public static function addToBlacklist(array $data = [], $proxy = null)
    {
        if (!static::$via_ip && !$proxy) {
            return false;
        }
        $insert_data = [
            'proxy_type' => static::$proxy_type,
            'proxy_ip' => static::$via_ip ?: static::getProxyIp($proxy),
            'blocked_by' => $data['source'],
            'blocked_expires' => static::getBlockedExpires($data['source']),
        ];
        $null_array = array_filter($insert_data, function($val){
            return $val === null;
        });
        if(count($null_array) > 0){
            static::$error = 'invalid data for add to black list:'.key($null_array);
            return false;
        }
        return static::getDB()->insert($insert_data);
    }


    /**
     * getBlockedExpires
     *
     * @param  string $source
     * @return string datetime
     */
    private static function getBlockedExpires($source = null)
    {
        return date('Y-m-d H:i:s', strtotime('+1 month'));
    }

    /**
     * getWaybills
     *
     * @param  string|null $brand
     * @return array
     */
    public static function getWaybills($limit = 10, $brand = null)
    {
        static $obj = null;
        if ($obj === null) {
            $obj = new ValidWaybillPool();
        }
        return $obj->get($limit, $brand);
    }

    /**
     * ipCheck
     *
     * @param  string $ip
     * @return boolean
     */
    public static function ipCheck($ip, $source = null)
    {
        $condition = ['proxy_ip' => $ip];
        $source !== null and is_string($source) and $condition['blocked_by'] = $source;
        return (bool)static::getDB()->count($condition);
    }

    /**
     * getBrandBySource
     *
     * @param  string $type
     * @return null|string
     */
    private static function getBrandBySource($type = null)
    {
        $source_brand_maps = [
            'ems_interface' => 'ems',
        ];
        return isset($source_brand_maps[$type]) ? $source_brand_maps[$type] : null;
    }


    /**
     * clearOldListInfos
     *
     * @param  string|null $time
     * @return mixed integer|boolean
     */
    public static function clearOldListInfos($time = null)
    {
        if (preg_match('/^\d{4}(?:-\d{2}){2}\s\d{2}(?:\:\d{2}){2}$/', $time)) {
            $condition = [
                'create_at <' => $time,
            ];
        } else {
            $condition = [
                'blocked_expires <' => date('Y-m-d H:i:s'),
            ];
        }
        return static::getDB()->delete($condition);
    }

    /**
     * checkAll sources
     *
     * @param  string $proxy
     * @param  integer $check_times
     * @param  string $via_ip
     * @return mixed
     */
    public static function checkAllSource($proxy, $check_times = 1, $via_ip = null)
    {
        $test_sources = static::getTestSources();
        return static::doCheck($proxy, $test_sources, null, $check_times, $via_ip);
    }

    /**
     * getError
     *
     * @return string
     */
    public static function getError()
    {
        return static::$error;
    }

    /**
     * repeater
     *
     * @param  callable $func
     * @param  array $params
     * @param  integer $times
     * @param  integer $wait_time
     * @return boolean|array
     */
    public static function repeater($func, array $params = [], $times = 3, $wait_time = 0)
    {
        // static::ENABLE_ANALYSIS and $timer['start'] = microtime(true);
        if (is_callable($func)) {
            do {
                if ($params[0] instanceof \Closure) {
                    $closure = $params[0];
                    $params[0] = $closure();
                } elseif (isset($closure)) {
                    $params[0] = $closure();
                }

                $data[$times] = call_user_func_array($func, $params);

                if (!$data[$times] || mb_strpos($data[$times], '快递公司参数异常') !== false) {
                    static::logRequest($data);
                    break;
                }
                if (count($data) > 20) {
                    static::logRequest($data);
                }
                (int)$wait_time > 0 and sleep((int)$wait_time);
            } while ($times-- > 0);
        }
        // static::ENABLE_ANALYSIS and $timer['end'] = microtime(true);
        return isset($data) ? $data : false;
    }

    /**
     * setTestSources 设置待测试的数据源
     *
     * @return array
     */
    private static function setTestSources($source = null)
    {
        if (is_string($source) && ($source = trim($source))) {
            static::$test_sources += [$source];
        } elseif (is_array($source)) {
            static::$test_sources = array_unique(array_merge(static::$test_sources, $source));
        }
    }

    /**
     * getTestSources 待测试的数据源
     *
     * @return array
     */
    private static function getTestSources()
    {
        return static::$test_sources;
    }

    /**
     * getProxyIp via http request
     *
     * @param  string $proxy
     * @return string|null
     */
    public static function getProxyIp($proxy = null)
    {
        $ip = null;
        if (static::isValidProxyFormat($proxy)) {
            $url = 'https://httpbin.org/ip';
            //http::curl传参数的实现是用对应的字符串或者去掉CURLOPT_
            if ($return = http::get($url, '', ['CURLOPT_PROXY' => $proxy])) {
                $return = json_decode($return, true);
                $ip = isset($return['origin']) ? $return['origin'] : null;
            }
        }
        return $ip;
    }

    /**
     * isValidProxyFormat
     *
     * @param  string  $proxy
     * @return boolean
     */
    private static function isValidProxyFormat($proxy)
    {
        return (bool) preg_match('/^(?:\d{1,3}\.){3}(?:\d{1,3}(?:\:\d{2,5}))$/', $proxy);
    }

    /**
     * getDB
     *
     * @param  string $table
     * @return \artisan\db;
     */
    private static function getDB($table = '')
    {
        if (static::$db === null) {
            static::$db = db::connect(static::$db_name);
        }
        return static::$db->table($table ?: static::$table_name);
    }
}
